/*
 * Copyright (c) 2008-2009 Ravenhearte Design
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * * Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * * Neither the name of 'Ravenhearte Design' nor the names of its contributors 
 *   may be used to endorse or promote products derived from this software 
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.ravenhearte.hardboild.gamelogic;

import java.nio.FloatBuffer;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.Random;

import org.ravenhearte.hardboild.assets.NodeAsset;
import org.ravenhearte.hardboild.assets.WalkController;
import org.ravenhearte.hardboild.assets.NodeAsset.AssetType;

import com.jme.intersection.BoundingPickResults;
import com.jme.intersection.PickResults;
import com.jme.light.PointLight;
import com.jme.math.Ray;
import com.jme.math.Vector2f;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Geometry;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.state.BlendState;
import com.jme.scene.state.LightState;
import com.jme.scene.state.ZBufferState;
import com.jme.system.DisplaySystem;

public class GameMap {
	private HashMap<String, GameTile> tiles;
	private HashMap<String, GameCharacter> characters;
	private LinkedList<GameCharacter> initiativeList;
	private ListIterator<GameCharacter> initiative;
	private GameCharacter currentCharacter;
	private LinkedList<String> placementLocations;
	private Node rootNode, worldGeometry, placementGeometry, tileGeometry, activeHex;
	
	public GameMap() {
		tiles = new HashMap<String, GameTile>();
		characters = new HashMap<String, GameCharacter>();
		placementLocations = new LinkedList<String>();
		tileGeometry = new Node("Tile Geometry");
		rootNode = new Node("Root Node");
		
		ZBufferState buf = DisplaySystem.getDisplaySystem().getRenderer().createZBufferState();
        buf.setEnabled(true);
        buf.setFunction(ZBufferState.TestFunction.LessThanOrEqualTo);
        tileGeometry.setRenderState(buf);
        rootNode.setRenderState(buf);
	}
	
	public void setGeometry(Node worldGeometry, Node placementGeometry) {
		this.worldGeometry = worldGeometry;
		this.placementGeometry = placementGeometry;
	}
	
	public Node getGeometry() {
		return rootNode;
	}
	
	public void addTile(GameTile tile) {
		tile.setParent(this);
		tiles.put(intArrayToString(tile.getLocation()), tile);
	}
	
	public void addPlacementLocation(int[] placementLocation) {
		placementLocations.add(intArrayToString(placementLocation));
	}
	
	public void walkCharacter(GameTile tile, GameCharacter character) {
		Skill moveSkill = character.getSkill("Move", "Basic Move");
		int movementRange = 4 + moveSkill.getRanking();
		
		LinkedList<GameTile> path = findPath(character.getTile(), tile, movementRange);
		
		WalkController walk = new WalkController(character, path);
		
		character.getSpatial().addController(walk);
		
		character.getTile().removeCharacter();
		tile.placeCharacter(character);
	}
	
	private LinkedList<GameTile> findPath(GameTile start, GameTile destination,
			int steps) {
		if(start == destination) {
			LinkedList<GameTile> temp = new LinkedList<GameTile>();
			temp.addFirst(start);
			return temp;			
		}
		
		if(steps == 0) {
			return null;
		}
		
		LinkedList<String> neighbors = new LinkedList<String>();

		LinkedList<String> possibleNeighbors = start.getPossibleNeighbors();
		ListIterator<String> it = possibleNeighbors.listIterator();
		while(it.hasNext()) {
			String tileKey = it.next();
			int[] tileLoc = GameMap.stringToIntArray(tileKey);
			tileLoc[2] -= 1;
			for(int j = 0; j < 3; j++) {
				int[] temp = tileLoc.clone();
				temp[2] += j;
				String tempKey = GameMap.intArrayToString(temp);
				if(tiles.containsKey(tempKey)) {
					if(!neighbors.contains(tempKey) &&
							!tiles.get(tempKey).isOccupied()) {
						neighbors.add(tempKey);
					}
				}
			}
		}

		it = neighbors.listIterator();
		LinkedList<GameTile> bestPath = null;
		while(it.hasNext()) {
			String tileKey = it.next();
			LinkedList<GameTile> temp = 
				findPath(tiles.get(tileKey), destination, steps - 1);
			if (temp != null) {
				temp.addFirst(start);
				if (bestPath == null || temp.size() < bestPath.size()) {
					bestPath = temp;
				}
			}
		}
		
		return bestPath;
	}
	
	public void placeCharacter(GameTile tile, GameCharacter character) {
		if(characters.containsKey(character.getName())) {
			if (character.getTile() == tile) {
				tile.removeCharacter();
				characters.remove(character.getName());
				rootNode.detachChild(character.getSpatial());
				rootNode.updateRenderState();
			}
			else {
				if(tile.isOccupied()) {
					GameCharacter occupier = tile.getOccupier();
					GameTile oldTile = character.getTile();
					tile.placeCharacter(character);
					oldTile.placeCharacter(occupier);

					int[] loc = occupier.getLocation();
					float xTrans, yTrans, zTrans;
					if(loc[1] % 2 != 0) {
						xTrans = (float)((loc[0] * 1.8) - (1.8 / 2));
					}
					else {
						xTrans = (float)((loc[0] * 1.8));
					}
					yTrans = (float)(loc[1] * 1.5);
					zTrans = (float)(loc[2] * .5);
					occupier.getSpatial().setLocalTranslation(xTrans, yTrans, zTrans);
				}
				else {
					character.getTile().removeCharacter();
					tile.placeCharacter(character);
				}
			}
		}
		else {
			if(tile.isOccupied()) {
				GameCharacter occupier = tile.getOccupier();
				tile.removeCharacter();
				characters.remove(occupier.getName());
				rootNode.detachChild(occupier.getSpatial());
			}
			characters.put(character.getName(), character);
			rootNode.attachChild(character.getSpatial());
			rootNode.updateRenderState();
			tile.placeCharacter(character);
		}

		int[] loc = character.getLocation();
		float xTrans, yTrans, zTrans;
		if(loc[1] % 2 != 0) {
			xTrans = (float)((loc[0] * 1.8) - (1.8 / 2));
		}
		else {
			xTrans = (float)((loc[0] * 1.8));
		}
		yTrans = (float)(loc[1] * 1.5);
		zTrans = (float)(loc[2] * .5);
		character.getSpatial().setLocalTranslation(xTrans, yTrans, zTrans);
	}
	
	public void placementStage() {
		rootNode.attachChild(placementGeometry);
		rootNode.updateRenderState();
		setTileGeometry(placementLocations, ColorRGBA.yellow);
	}

	public void fightStage() {
		rootNode.detachChild(placementGeometry);
		rootNode.attachChild(worldGeometry);
		rootNode.updateRenderState();
		tileGeometry.detachAllChildren();
	}
	
	public GameCharacter getCurrentCharacter() {
		return currentCharacter;
	}
	
	public void generateInitiative() {
		initiativeList = new LinkedList<GameCharacter>();
		Iterator<GameCharacter> it = characters.values().iterator();
		
		while(it.hasNext()) {
			GameCharacter current = it.next();
			Skill charInitiative = current.getSkill("Move", "Initiative");
			int initRanking = charInitiative.getRanking();
			
			ListIterator<GameCharacter> lit = initiativeList.listIterator();
			if (! lit.hasNext() ) {
				lit.add(current);
			}
			else {
				boolean placed = false;
				while (lit.hasNext() && !placed) {
					GameCharacter next = lit.next();
					Skill nextInitiative = next.getSkill("Move", "Initiative");
					int nextRanking = nextInitiative.getRanking();
					
					if (initRanking > nextRanking) {
						lit.previous();
						lit.add(current);
						placed = true;
					}
				}
				if (!placed) {
					lit.add(current);
				}
			}
		}
		
		initiative = initiativeList.listIterator();
		currentCharacter = initiative.next();
	}
	
	public void advanceInitiative() {
		if(initiative.hasNext()) {
			currentCharacter = initiative.next();
		}
		else {
			initiative = initiativeList.listIterator();
			currentCharacter = initiative.next();
		}
	}
	
	public GameTile findMousePick(int xCoord, int yCoord)
	{
		Camera cam = DisplaySystem.getDisplaySystem().getRenderer().getCamera();
		Vector2f screenPos = new Vector2f(xCoord, yCoord);
		Vector3f startPoint = cam.getWorldCoordinates(screenPos, 0);
		Vector3f endPoint = cam.getWorldCoordinates(screenPos, 1);
		Ray ray = new Ray(startPoint, endPoint);

		PickResults results =  new BoundingPickResults();
		results.clear();
		results.setCheckDistance(true);
		tileGeometry.findPick(ray, results);
		
		if (results.getNumber() != 0) {
			for(int i=0; i < results.getNumber(); i++) {
				Node picked = (Node) results.getPickData(i).getTargetMesh().
					getParent().getParent();
				String locKey = picked.getName().replace("Hex", "");
				if(tiles.containsKey(locKey)) {
					return tiles.get(locKey);
				}
			}
		}
		
		return null;
	}
	
	public GameTile randomTile() {
		Random rand = new Random();
		int whichTile = rand.nextInt(tiles.size());
		
		Iterator<GameTile> it = tiles.values().iterator();
		GameTile randTile = it.next();
		
		for (int i=0; i < whichTile; i++) {
			 randTile = it.next();
		}
		
		if(randTile.isOccupied()) {
			return randomTile();
		}
		else {
			return randTile;
		}
	}
	
	public HashMap<String, GameTile> getTiles() {
		return tiles;
	}
	
	public LinkedList<String> getPlacementLocs() {
		return placementLocations;
	}
	
	public void clearHighlight() {
		if(activeHex != null) {
			Geometry child = (Geometry)((Node)activeHex.getChild(0)).getChild(0);
			FloatBuffer colorBuf = child.getColorBuffer();
			ColorRGBA color = new ColorRGBA(colorBuf.get(0), colorBuf.get(1),
					colorBuf.get(2), colorBuf.get(3));
			color.a = (float) .5;
			child.setSolidColor(color);
			activeHex = null;
		}
	}
	
	public void highlightTile(GameTile tile) {
		int[] loc = tile.getLocation();
		Node hex = (Node)tileGeometry.getChild("Hex"+intArrayToString(loc));
		
		if(activeHex == hex) {
			return;
		}
		else if (activeHex == null) {
			Geometry child = (Geometry)((Node)hex.getChild(0)).getChild(0);
			FloatBuffer colorBuf = child.getColorBuffer();
			ColorRGBA color = new ColorRGBA(colorBuf.get(0), colorBuf.get(1),
					colorBuf.get(2), colorBuf.get(3));
			color.a = 1;
			child.setSolidColor(color);
			activeHex = hex;
		}
		else {
			Geometry child = (Geometry)((Node)hex.getChild(0)).getChild(0);
			FloatBuffer colorBuf = child.getColorBuffer();
			ColorRGBA color = new ColorRGBA(colorBuf.get(0), colorBuf.get(1),
					colorBuf.get(2), colorBuf.get(3));
			color.a = 1;
			child.setSolidColor(color);
			
			child = (Geometry)((Node)activeHex.getChild(0)).getChild(0);
			color.a = (float) .5;
			child.setSolidColor(color);
			
			activeHex = hex;
		}
	}
	
	public Node getTileGeometry() {
		return tileGeometry;
	}
	
	public static int[] stringToIntArray(String value) {
		int[] intArray = new int[3];

		intArray[0] = Integer.parseInt(value.substring(0, 3));
		intArray[1] = Integer.parseInt(value.substring(3, 6));
		intArray[2] = Integer.parseInt(value.substring(6, 9));
		
		return intArray;
	}
	
	public static String intArrayToString(int[] value) {
		return intToString(value[0]) + intToString(value[1]) + intToString(value[2]);
	}
	
	private static String intToString(int value) {
		if (value < 0) {
			value *= -1;
		}
		if (value < 10) {
			return "00"+value;
		}
		else if (value < 100) {
			return "0"+value;
		}
		else {
			return "" + value;
		}
	}
	
	public void setTileGeometry(LinkedList<String> tileLocations, ColorRGBA color) {
		tileGeometry.detachAllChildren();
		
		if(tileLocations == null) {
			return;
		}
		
		ListIterator<String> it = tileLocations.listIterator();
		
		while(it.hasNext()) {
			String locString = it.next();
			int[] loc = stringToIntArray(locString);
			NodeAsset hex = new NodeAsset("Hex"+locString, AssetType.MODEL);
			hex.load("Hex");
			float xTrans, yTrans, zTrans;
			if(loc[1] % 2 != 0) {
				xTrans = (float)((loc[0] * 1.8) - (1.8 / 2));
			}
			else {
				xTrans = (float)((loc[0] * 1.8));
			}
			yTrans = (float)(loc[1] * 1.5);
			zTrans = (float)(((float)loc[2] * .5f) + .01f);
			hex.get().setLocalTranslation(xTrans, yTrans, zTrans);
			
			Geometry child = (Geometry)((Node)hex.get().getChild(0)).getChild(0);
			color.a = (float) .5;
			child.setSolidColor(color);
			      
			BlendState as1 = DisplaySystem.getDisplaySystem().getRenderer().createBlendState();
			as1.setBlendEnabled(true);
			as1.setSourceFunctionAlpha(BlendState.SourceFunction.SourceAlpha);
			as1.setDestinationFunctionAlpha(BlendState.DestinationFunction.OneMinusSourceAlpha);
			as1.setEnabled(true);
			hex.get().setRenderState(as1);

			tileGeometry.attachChild(hex.get());
		}
		
		tileGeometry.updateRenderState();
	}
}